{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4cd4f701",
   "metadata": {},
   "source": [
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-4/map-reduce.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239947-lesson-3-map-reduce)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "36737349-c949-4d64-9aa3-3767cbd02ad1",
   "metadata": {},
   "source": [
    "# Map-reduce\n",
    "\n",
    "## Review\n",
    "\n",
    "We're building up to a multi-agent research assistant that ties together all of the modules from this course.\n",
    "\n",
    "To build this multi-agent assistant, we've been introducing a few LangGraph controllability topics.\n",
    "\n",
    "We just covered parallelization and sub-graphs.\n",
    "\n",
    "## Goals\n",
    "\n",
    "Now, we're going to cover [map reduce](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f24e95c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install -U langchain_openai langgraph"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff57cbf7",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os, getpass\n",
    "\n",
    "def _set_env(var: str):\n",
    "    if not os.environ.get(var):\n",
    "        os.environ[var] = getpass.getpass(f\"{var}: \")\n",
    "\n",
    "_set_env(\"OPENAI_API_KEY\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbcd868a",
   "metadata": {},
   "source": [
    "We'll use [LangSmith](https://docs.smith.langchain.com/) for [tracing](https://docs.smith.langchain.com/concepts/tracing)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9fdc647f",
   "metadata": {},
   "outputs": [],
   "source": [
    "_set_env(\"LANGCHAIN_API_KEY\")\n",
    "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
    "os.environ[\"LANGCHAIN_PROJECT\"] = \"langchain-academy\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2bbe9b9f-4375-4bca-8e32-7d57cb861469",
   "metadata": {},
   "source": [
    "## Problem\n",
    "\n",
    "Map-reduce operations are essential for efficient task decomposition and parallel processing. \n",
    "\n",
    "It has two phases:\n",
    "\n",
    "(1) `Map` - Break a task into smaller sub-tasks, processing each sub-task in parallel.\n",
    "\n",
    "(2) `Reduce` - Aggregate the results across all of the completed, parallelized sub-tasks.\n",
    "\n",
    "Let's design a system that will do two things:\n",
    "\n",
    "(1) `Map` - Create a set of jokes about a topic.\n",
    "\n",
    "(2) `Reduce` - Pick the best joke from the list.\n",
    "\n",
    "We'll use an LLM to do the job generation and selection."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "994cf903-1ed6-4ae2-b32a-7891a2808f81",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "# Prompts we will use\n",
    "subjects_prompt = \"\"\"Generate a list of 3 sub-topics that are all related to this overall topic: {topic}.\"\"\"\n",
    "joke_prompt = \"\"\"Generate a joke about {subject}\"\"\"\n",
    "best_joke_prompt = \"\"\"Below are a bunch of jokes about {topic}. Select the best one! Return the ID of the best one, starting 0 as the ID for the first joke. Jokes: \\n\\n  {jokes}\"\"\"\n",
    "\n",
    "# LLM\n",
    "model = ChatOpenAI(model=\"gpt-4o\", temperature=0) "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f3b883cc-3469-4e96-b1a4-deadf7bf3ce5",
   "metadata": {},
   "source": [
    "## State\n",
    "\n",
    "### Parallelizing joke generation\n",
    "\n",
    "First, let's define the entry point of the graph that will:\n",
    "\n",
    "* Take a user input topic\n",
    "* Produce a list of joke topics from it\n",
    "* Send each joke topic to our above joke generation node\n",
    "\n",
    "Our state has a `jokes` key, which will accumulate jokes from parallelized joke generation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "099218ca-ee78-4291-95a1-87ee61382e3b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import operator\n",
    "from typing import Annotated\n",
    "from typing_extensions import TypedDict\n",
    "from pydantic import BaseModel\n",
    "\n",
    "class Subjects(BaseModel):\n",
    "    subjects: list[str]\n",
    "\n",
    "class BestJoke(BaseModel):\n",
    "    id: int\n",
    "    \n",
    "class OverallState(TypedDict):\n",
    "    topic: str\n",
    "    subjects: list\n",
    "    jokes: Annotated[list, operator.add]\n",
    "    best_selected_joke: str"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c7176d1c-4a88-4b0f-a960-ee04a45279bd",
   "metadata": {},
   "source": [
    "Generate subjects for jokes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "45010efd-ad31-4daa-b77e-aaec79ef0309",
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_topics(state: OverallState):\n",
    "    prompt = subjects_prompt.format(topic=state[\"topic\"])\n",
    "    response = model.with_structured_output(Subjects).invoke(prompt)\n",
    "    return {\"subjects\": response.subjects}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5296bb0-c163-4e5c-8181-1e305b37442a",
   "metadata": {},
   "source": [
    "Here is the magic: we use the [Send](https://langchain-ai.github.io/langgraph/concepts/low_level/#send) to create a joke for each subject.\n",
    "\n",
    "This is very useful! It can automatically parallelize joke generation for any number of subjects.\n",
    "\n",
    "* `generate_joke`: the name of the node in the graph\n",
    "* `{\"subject\": s`}: the state to send\n",
    "\n",
    "`Send` allow you to pass any state that you want to `generate_joke`! It does not have to align with `OverallState`.\n",
    "\n",
    "In this case, `generate_joke` is using its own internal state, and we can popular this via `Send`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "bc83e575-11f6-41a9-990a-adb571bcda06",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.constants import Send\n",
    "def continue_to_jokes(state: OverallState):\n",
    "    return [Send(\"generate_joke\", {\"subject\": s}) for s in state[\"subjects\"]]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9847192d-d358-411e-90c0-f06be0738717",
   "metadata": {},
   "source": [
    "### Joke generation (map)\n",
    "\n",
    "Now, we just define a node that will create our jokes, `generate_joke`!\n",
    "\n",
    "We write them back out to `jokes` in `OverallState`! \n",
    "\n",
    "This key has a reducer that will combine lists."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "bcddc567-73d3-4fb3-bfc5-1bea538f2aab",
   "metadata": {},
   "outputs": [],
   "source": [
    "class JokeState(TypedDict):\n",
    "    subject: str\n",
    "\n",
    "class Joke(BaseModel):\n",
    "    joke: str\n",
    "\n",
    "def generate_joke(state: JokeState):\n",
    "    prompt = joke_prompt.format(subject=state[\"subject\"])\n",
    "    response = model.with_structured_output(Joke).invoke(prompt)\n",
    "    return {\"jokes\": [response.joke]}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02960657-d174-4076-99a8-b3f9eea015f4",
   "metadata": {},
   "source": [
    "### Best joke selection (reduce)\n",
    "\n",
    "Now, we add logic to pick the best joke."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "8d672870-75e3-4307-bda0-c41a86cbbaff",
   "metadata": {},
   "outputs": [],
   "source": [
    "def best_joke(state: OverallState):\n",
    "    jokes = \"\\n\\n\".join(state[\"jokes\"])\n",
    "    prompt = best_joke_prompt.format(topic=state[\"topic\"], jokes=jokes)\n",
    "    response = model.with_structured_output(BestJoke).invoke(prompt)\n",
    "    return {\"best_selected_joke\": state[\"jokes\"][response.id]}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "837cd12e-5bff-426e-97f4-c774df998cfb",
   "metadata": {},
   "source": [
    "## Compile"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "2ae6be4b-144e-483c-88ad-ce86d6477a0d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from IPython.display import Image\n",
    "from langgraph.graph import END, StateGraph, START\n",
    "\n",
    "# Construct the graph: here we put everything together to construct our graph\n",
    "graph = StateGraph(OverallState)\n",
    "graph.add_node(\"generate_topics\", generate_topics)\n",
    "graph.add_node(\"generate_joke\", generate_joke)\n",
    "graph.add_node(\"best_joke\", best_joke)\n",
    "graph.add_edge(START, \"generate_topics\")\n",
    "graph.add_conditional_edges(\"generate_topics\", continue_to_jokes, [\"generate_joke\"])\n",
    "graph.add_edge(\"generate_joke\", \"best_joke\")\n",
    "graph.add_edge(\"best_joke\", END)\n",
    "\n",
    "# Compile the graph\n",
    "app = graph.compile()\n",
    "Image(app.get_graph().draw_mermaid_png())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "e21dc7c9-0add-4125-be76-af701adb874a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'generate_topics': {'subjects': ['mammals', 'reptiles', 'birds']}}\n",
      "{'generate_joke': {'jokes': [\"Why don't mammals ever get lost? Because they always follow their 'instincts'!\"]}}\n",
      "{'generate_joke': {'jokes': [\"Why don't alligators like fast food? Because they can't catch it!\"]}}\n",
      "{'generate_joke': {'jokes': [\"Why do birds fly south for the winter? Because it's too far to walk!\"]}}\n",
      "{'best_joke': {'best_selected_joke': \"Why don't alligators like fast food? Because they can't catch it!\"}}\n"
     ]
    }
   ],
   "source": [
    "# Call the graph: here we call it to generate a list of jokes\n",
    "for s in app.stream({\"topic\": \"animals\"}):\n",
    "    print(s)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "2a96517e-77ab-46e2-95e2-79168c044e9c",
   "metadata": {},
   "source": [
    "## Studio\n",
    "\n",
    "--\n",
    "\n",
    "**⚠️ DISCLAIMER**\n",
    "\n",
    "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n",
    "\n",
    "*Also, if you are running this notebook in CoLab, then skip this step.*\n",
    "\n",
    "--\n",
    "\n",
    "Let's load our the above graph in the Studio UI, which uses `module-4/studio/map_reduce.py` set in `module-4/studio/langgraph.json`.\n",
    "\n",
    "![Screenshot 2024-08-28 at 3.17.53 PM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbb0c0ed88a12e822811e2_map-reduce1.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "741a5e45-9a4c-43b4-8393-9298b3dcda53",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
